using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using JetBrains.Annotations;
using SF.Code.Extension.WinAPI;

namespace SG.Productivity.DesktopControlPanel.Support
{
    public delegate void MouseHookCallback(ref MouseHookStruct data);

    public delegate void MouseLowLevelHookCallback(ref MouseLowLevelHookStruct data);

    public static class MouseHookHelper
    {
        //Declare the hook handle as an int.
        private static int _hHook;
        // Callbacks
        private static MouseHookCallback _callback;
        private static MouseLowLevelHookCallback _lowLevelCallback;
        [UsedImplicitly] private static User32.HookProc _mouseHookProc;

        public static int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            //Marshall the data from the callback.
            var hookStruct = (MouseHookStruct) Marshal.PtrToStructure(lParam, typeof (MouseHookStruct));

            if (nCode < 0)
                return User32.CallNextHookEx(_hHook, nCode, wParam, lParam);
            /*
            //Create a string variable that shows the current mouse coordinates.
            var strCaption =
                string.Format("wParam = {5:x} x = {0:d} y = {1:d} hwnd = {2:x} testCode = {3:x} extra = {4:x}",
                              hookStruct.pt.x, hookStruct.pt.y, hookStruct.hwnd, hookStruct.wHitTestCode,
                              hookStruct.dwExtraInfo.ToInt64(), wParam.ToInt64());

                         */
            var callback = _callback;

            if (callback != null)
                callback(ref hookStruct);

            return User32.CallNextHookEx(_hHook, nCode, wParam, lParam);
        }

        public static int MouseLLHookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode < 0)
                return User32.CallNextHookEx(_hHook, nCode, wParam, lParam);
            //Marshall the data from the callback.
            var hookStruct = (MouseLowLevelHookStruct) Marshal.PtrToStructure(lParam, typeof (MouseLowLevelHookStruct));
        
            /*
            //Create a string variable that shows the current mouse coordinates.

            var strCaption =
                string.Format(
                    "wParam = {6:x} x = {0:d} y = {1:d} flags = {2:x} mouseData = {3:x} extra = {4:x} time = {5:x}",
                    hookStruct.pt.x, hookStruct.pt.y, hookStruct.flags, hookStruct.mouseData,
                    hookStruct.dwExtraInfo.ToInt64(), hookStruct.time, wParam.ToInt64());
            */
            var callback = _lowLevelCallback;

            if (callback != null)
                callback(ref hookStruct);

            return User32.CallNextHookEx(_hHook, nCode, wParam, lParam);
        }

        public static void Unregister()
        {
            if (_hHook == 0)
                return;

            Toggle();
        }

        public static void Toggle(MouseHookCallback callback = null, MouseLowLevelHookCallback lowLevelCallback = null)
        {
            if (_hHook == 0)
            {
                // Create an instance of HookProc.

                if (callback != null)
                {
                    _callback = callback;
                    _hHook = User32.SetWindowsHookEx(HookType.WH_MOUSE,
                                                     _mouseHookProc = MouseHookProc,
                                                     IntPtr.Zero,
                                                     Thread.CurrentThread.ManagedThreadId);
                }
                else if (lowLevelCallback != null)
                {
                    _lowLevelCallback = lowLevelCallback;
                    using (var curProcess = Process.GetCurrentProcess())
                    using (var curModule = curProcess.MainModule)
                    {
                        _hHook = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL,
                                                         _mouseHookProc = MouseLLHookProc,
                                                         Kernel32.GetModuleHandle(curModule.ModuleName), 0);
                    }
                }

                //If the SetWindowsHookEx function fails.
                if (_hHook == 0)
                {
                    throw new Exception("SetWindowsHookEx Failed");
                }
            }
            else
            {
                _callback = null;
                _lowLevelCallback = null;
                var ret = User32.UnhookWindowsHookEx(_hHook);
                //If the UnhookWindowsHookEx function fails.
                if (ret == false)
                {
                    throw new Exception("UnhookWindowsHookEx Failed");
                }
                _hHook = 0;
            }
        }

    }
}